/*
 Copyright 2007 Ole Rogeberg

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
package org.lifeyears.model.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Embeddable;
import javax.persistence.Lob;
import javax.persistence.Transient;

import org.lifeyears.model.AbstractEntity;

/**
 * Class to generate string representations of the entity classes in lifeyears.
 * 
 * @author Aleksander Blomskøld
 */
public class ToStringUtil
{

    /**
     * Get all fields (also private) that belongs to a class along with all fields that belongs to
     * all of the classes subclasses. If a field named 'id' is found, it is put in the front of the
     * list.
     */
    private static List<Field> getAllFields( Class<?> clazz )
    {
        // get all fields declared by this class and all super-classes
        final List<Field> fieldList = new ArrayList<Field>();

        while ( clazz != null )
        {
            final Field[] fields = clazz.getDeclaredFields();

            for ( final Field field : fields )
            {
                // put the id-field in front
                field.setAccessible( true );
                if ( field.getName().equals( "id" ) )
                {
                    fieldList.add( 0, field );
                }
                else
                {
                    fieldList.add( field );
                }
            }
            clazz = clazz.getSuperclass();
        }

        return fieldList;
    }

    /**
     * Creates a proper String-representation of lifeyears domain objects.
     */
    public static String toString( final Object object )
    {
        if ( object == null )
        {
            return "null";
        }

        try
        {
            final StringBuilder sb = new StringBuilder( 100 );

            final Class<?> clazz = object.getClass();

            sb.append( clazz.getSimpleName() ).append( "[" );

            final List<Field> fieldList = getAllFields( clazz );

            for ( final Field field : fieldList )
            {
                field.setAccessible( true );
                final Set<Class<?>> annotations = new HashSet<Class<?>>();

                for ( final Annotation a : field.getAnnotations() )
                {
                    annotations.add( a.getClass() );
                }

                // check if static
                if ( Modifier.isStatic( field.getModifiers() ) )
                {
                    continue;
                }

                // check if transient
                if ( annotations.contains( Transient.class ) )
                {
                    continue;
                }

                final Class<?> fieldClass = field.getType();
                if ( fieldClass.isPrimitive() || fieldClass.getPackage().getName().equals( "java.lang" ) || fieldClass.equals( Date.class ) )
                {
                    // we have a primitive, String or similar:
                    sb.append( field.getName() ).append( "=" );
                    if ( fieldClass == Long.class || fieldClass == long.class || fieldClass == Integer.class || fieldClass == int.class
                        || fieldClass == Short.class || fieldClass == short.class || fieldClass == Byte.class || fieldClass == byte.class
                        || fieldClass == Double.class || fieldClass == double.class || fieldClass == Float.class || fieldClass == float.class )
                    {
                        // we have a number, dont use ''
                        sb.append( field.get( object ) );

                    }
                    else
                    {

                        // check that its not a Lob:

                        if ( annotations.contains( (Lob.class) ) )
                        {
                            sb.append( "[LOB]" );
                        }
                        else
                        {
                            final Object f = field.get( object );
                            if ( f != null )
                            {
                                sb.append( "'" );
                            }
                            sb.append( f );
                            if ( f != null )
                            {
                                sb.append( "'" );
                            }
                        }
                    }
                    sb.append( ' ' );
                }
                else if ( fieldClass.getPackage().getName().startsWith( "org.lifeyears.model" ) )
                {
                    final Object fieldObject = field.get( object );
                    if ( annotations.contains( Embeddable.class ) )
                    {
                        sb.append( field.getName() ).append( "={" ).append( field.get( object ).toString() ).append( "} " );
                    }
                    else if ( fieldObject instanceof AbstractEntity || fieldObject == null )
                    {
                        final AbstractEntity abo = (AbstractEntity) fieldObject;
                        // we have a model class:
                        sb.append( field.getName() ).append( "Id=" );
                        if ( fieldObject == null )
                        {
                            sb.append( "null" );
                        }
                        else
                        {
                            sb.append( abo.getId() );
                        }

                        sb.append( " " );
                    }

                }

            }
            sb.append( "]" );
            return sb.toString();
        }
        catch ( final IllegalAccessException e )
        {
            // this should never happen
            throw new RuntimeException( e );
        }
    }

    /**
     * 
     * Go through all the field's annotations and return the first annotation of the given class.
     * Returns null if no annotation found.
     */
    @SuppressWarnings( "unchecked" )
    public static <T> T getAnnotation( final Field field, final Class<T> clazz )
    {
        field.setAccessible( true );
        for ( final Annotation ann : field.getAnnotations() )
        {
            if ( ann.annotationType().equals( clazz ) )
            {
                return (T) ann;
            }
        }
        // annotation not found
        return null;
    }
}
